package com.bluexmicro.android.coroutineble

import android.annotation.SuppressLint
import android.bluetooth.*
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.bluexmicro.android.coroutineble.model.BleRequest
import com.bluexmicro.android.coroutineble.model.BleResponse
import com.bluexmicro.android.coroutineble.model.ConnectionState
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.jvm.Throws
import kotlin.math.log

private const val CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"
private val UUID_CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG)

/**
 * 参考【BleGattCoroutines
 * https://github.com/Beepiz/BleGattCoroutines
 */
@SuppressLint("MissingPermission")
class BluetoothPeripheral  constructor(
    private val context: Context,
    private val device: BluetoothDevice,
) {

    private val TAG = "Peripheral(${device.address})"

    // LOCK
    private val bleOperationMutex = Mutex()
    private var autoConnect = false
    private var mGatt: BluetoothGatt? = null

    /**
     * 协程Scope,生命周期应控制在和连接状态一致,
     * 管理除连接外的所有连接
     */
    private var coroutineScope = CoroutineScope(Dispatchers.Default)

    private val _connectionState = MutableStateFlow(ConnectionState.IDLE)
    private val _requestShare = MutableSharedFlow<BleRequest>()
    private val _responseShare = MutableSharedFlow<BleResponse>()

    private val internalResponseChannel = Channel<BleResponse>()

    val connectionState: StateFlow<ConnectionState> = _connectionState
    val requestShared: SharedFlow<BleRequest> = _requestShare
    val responseShared: SharedFlow<BleResponse> = _responseShare

    //主动断开
    private var activeClose = false

    private val gattCallback = object : BluetoothGattCallback() {

        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            _connectionState.value = when (newState) {
                BluetoothProfile.STATE_CONNECTED -> ConnectionState.CONNECTED
                BluetoothProfile.STATE_CONNECTING -> ConnectionState.CONNECTING
                BluetoothProfile.STATE_DISCONNECTED -> {
                    if (mGatt == null) {
                        ConnectionState.FAILED
                    } else {
                        if (status == 133) {
                            gatt.close()
                            refreshDeviceCache()
                        }
                        val result =
                            if (activeClose) ConnectionState.DISCONNECTED else ConnectionState.LOST
                        activeClose = false
                        result
                    }
                }
                BluetoothProfile.STATE_DISCONNECTING -> ConnectionState.DISCONNECTING
                else -> ConnectionState.FAILED
            }
            Log.e(TAG, "onConnectionStateChange: ${_connectionState.value.name}")
            BleResponse.OnConnectionStateChange(_connectionState.value).report()
        }

        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            BleResponse.OnServicesDiscovered(gatt.services, status).report()
        }

        override fun onDescriptorWrite(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            BleResponse.OnDescriptorWrite(descriptor, status).report()
        }

        override fun onDescriptorRead(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            BleResponse.OnDescriptorRead(descriptor, status).report()
        }

        override fun onCharacteristicRead(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            BleResponse.OnCharacteristicRead(characteristic, status).report()
        }

        override fun onCharacteristicWrite(
            gatt: BluetoothGatt?,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            BleResponse.OnCharacteristicWrite(characteristic, status).report()
        }

        override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic
        ) {
            BleResponse.OnCharacteristicChanged(characteristic).report()
        }

        override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
            BleResponse.OnMtuChanged(mtu, status).report()
        }

        override fun onPhyUpdate(gatt: BluetoothGatt, txPhy: Int, rxPhy: Int, status: Int) {
            super.onPhyUpdate(gatt, txPhy, rxPhy, status)
        }

    }

    suspend fun connect(phy: Int? = null): Boolean {
        if (!coroutineScope.isActive) coroutineScope = CoroutineScope(Dispatchers.Default)
        val request = BleRequest.Connect()
        if (isConnectionClosed()) return false
        if (context.connectReady()) {
            if (isConnectionClosed()) return false
            var errorMsg: String? = null
            mGatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && phy != null) {
                //ViVO Android 8.1无法使用2M phy登录
//                val phy =
//                    BluetoothDevice.PHY_LE_2M_MASK or BluetoothDevice.PHY_LE_1M_MASK or BluetoothDevice.PHY_LE_CODED_MASK
                device.connectGatt(
                    context,
                    autoConnect,
                    gattCallback,
                    BluetoothDevice.TRANSPORT_LE,
                    phy
                )
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                device.connectGatt(
                    context,
                    autoConnect,
                    gattCallback,
                    BluetoothDevice.TRANSPORT_LE
                )
            } else {
                device.connectGatt(context, autoConnect, gattCallback)
            }
            if (mGatt == null) {
                request.report(errorMsg ?: "Native method exception")
                return false
            }
            _connectionState.value = ConnectionState.CONNECTING
            val state = _connectionState.first {
                when (it) {
                    ConnectionState.CONNECTED, ConnectionState.DISCONNECTED, ConnectionState.LOST, ConnectionState.FAILED -> true
                    else -> false
                }
            }
            return state == ConnectionState.CONNECTED
        } else {
            return false
        }
    }


    private fun isConnectionClosed(): Boolean {
        return !coroutineScope.isActive
    }

    fun disconnect() {
        if (context.connectReady() && mGatt != null) {
            activeClose = true
            BleRequest.Disconnect().report()
            mGatt!!.disconnect()
        }
    }

    /**
     * **************** 当release后，要想重新使用该peripheral请允许setup方法,或者重新创建对象*******
     * @see setup
     */
    fun release() {
        refreshDeviceCache()
        mGatt?.disconnect()
        mGatt?.close()
        internalResponseChannel.close()
        coroutineScope.cancel()
        mGatt = null
    }

    fun setup() {
        coroutineScope = CoroutineScope(Dispatchers.Default)
        //internalResponseChannel todo
    }

    suspend fun discoverServices(): List<BluetoothGattService> = withContext(Dispatchers.Default) {
        val request = BleRequest.DiscoverServices()
        val response = request.gattExecute<BleResponse.OnServicesDiscovered> {
            discoverServices()
        }
        return@withContext response?.takeIf { it.isSuccess() }?.run { this.services } ?: emptyList()
    }

    fun getServices(): List<BluetoothGattService> {
        return mGatt?.services ?: emptyList()
    }

    fun findService(uuid: UUID): BluetoothGattService? {
        return mGatt?.getService(uuid)
    }

    fun BluetoothGattService.findCharacteristic(uuid: UUID): BluetoothGattCharacteristic? {
        return getCharacteristic(uuid)
    }

    suspend fun writeDescriptor(
        descriptor: BluetoothGattDescriptor,
        value: ByteArray
    ): Boolean = withContext(Dispatchers.Default) {
        val response = BleRequest.WriteDescriptor(descriptor, value)
            .gattExecute<BleResponse.OnDescriptorWrite> {
                descriptor.value = value
                writeDescriptor(descriptor)
            }
        return@withContext response?.isSuccess() ?: false
    }

    suspend fun readDescriptor(descriptor: BluetoothGattDescriptor): ByteArray? =
        withContext(Dispatchers.Default) {
            val response =
                BleRequest.ReadDescriptor(descriptor).gattExecute<BleResponse.OnDescriptorRead> {
                    readDescriptor(descriptor)
                }
            return@withContext response?.takeIf { it.isSuccess() }?.run { this.descriptor.value }
        }

    suspend fun read(characteristic: BluetoothGattCharacteristic): ByteArray? =
        withContext(Dispatchers.Default) {
            val request = BleRequest.Read(characteristic)
            if (!characteristic.hasProperty(BluetoothGattCharacteristic.PROPERTY_READ)) {
                request.report("This characteristic does not support Read!")
                return@withContext null
            }
            val response = request.gattExecute<BleResponse.OnCharacteristicRead> {
                readCharacteristic(characteristic)
            }
            return@withContext response?.takeIf { it.isSuccess() }
                ?.run { this.characteristic.value }
        }

    suspend fun write(
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray,
    ): Boolean = withContext(Dispatchers.Main) {
        val request = BleRequest.Write(characteristic, value)
        val response = request.gattExecute<BleResponse.OnCharacteristicWrite> {
            characteristic.value = value
            characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
            writeCharacteristic(characteristic)
        }
        return@withContext response?.isSuccess() ?: false
    }

    suspend fun writeWithoutResponse(
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray
    ): Boolean = withContext(Dispatchers.Default) {
        val request = BleRequest.Write(characteristic, value)
        val response = request.gattExecute<BleResponse.OnCharacteristicWrite> {
            characteristic.value = value
            characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
            writeCharacteristic(characteristic)
        }
        return@withContext response?.isSuccess() ?: false
    }

    suspend fun writeSigned(
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray,
    ): Boolean = withContext(Dispatchers.Default) {
        val request = BleRequest.Write(characteristic, value)
        val response = request.gattExecute<BleResponse.OnCharacteristicWrite> {
            characteristic.value = value
            characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED
            writeCharacteristic(characteristic)
        }
        return@withContext response?.isSuccess() ?: false
    }

    /**
     * 请求更换MTU（Max Transfer Unit）
     * 这是一个协商的过程，以双方均支持的值进行交互（迁就弱者原则）
     * @sample
     * ① 向外设请求512，外设最大支持251，那么外设回应251，双方以最大251交互
     * ② 向外设请求128，外设最大支持512，那么外设回应128，双方以最大128交互
     */
    suspend fun requestMtu(size: Int): Int? = withContext(Dispatchers.Default) {
        val response = BleRequest.RequestMTU(size).gattExecute<BleResponse.OnMtuChanged> {
            requestMtu(size)
        }
        return@withContext response?.takeIf { it.isSuccess() }?.run { this.mtu }
    }

    /**
     * @param connectionPriority
     * @see BluetoothGatt.CONNECTION_PRIORITY_BALANCED 0
     * @see BluetoothGatt.CONNECTION_PRIORITY_HIGH 1
     * @see BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER 2
     */
    fun requestConnectionPriority(connectionPriority: Int): Boolean {
        //因为callback不对应用层开放，这里请求成功就默认response成功
        val result = mGatt?.requestConnectionPriority(connectionPriority) ?: false
        if (!result) {
            BleRequest.RequestConnectionProperty(connectionPriority)
                .report("Native method exception")
        }
        return result
    }

    @RequiresApi(Build.VERSION_CODES.O)
    fun requestPhy() {
        mGatt?.setPreferredPhy(
            BluetoothDevice.PHY_LE_2M_MASK,
            BluetoothDevice.PHY_LE_2M_MASK,
            BluetoothDevice.PHY_OPTION_NO_PREFERRED
        )
    }

    /**
     * @param defaultEnable 某些不符合规范的外设是默认使能的
     */
    suspend fun toggleNotification(
        characteristic: BluetoothGattCharacteristic,
        enable: Boolean,
        defaultEnable: Boolean = false
    ): Boolean = withContext(Dispatchers.Default) {
        val notify = characteristic.hasProperty(BluetoothGattCharacteristic.PROPERTY_NOTIFY)
        val indicate = characteristic.hasProperty(BluetoothGattCharacteristic.PROPERTY_INDICATE)
        val notifyRequest = BleRequest.SetNotification(characteristic, enable)
        return@withContext if (notify || indicate) {
            val result = mGatt?.setCharacteristicNotification(characteristic, enable) ?: false
            if (!result) {
                notifyRequest.report("Native method exception")
                return@withContext false
            }
            notifyRequest.report()
            if (!defaultEnable) {
                val descriptor: BluetoothGattDescriptor? =
                    characteristic.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIG)
                if (descriptor == null) {
                    BleRequest.Other("Write descriptor ($UUID_CLIENT_CHARACTERISTIC_CONFIG)")
                        .report("The characteristic missing descriptor ($UUID_CLIENT_CHARACTERISTIC_CONFIG)")
                    return@withContext false
                }
                val cmd = if (enable) {
                    if (notify) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE else BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
                } else {
                    BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
                }
                writeDescriptor(descriptor, cmd)
            } else {
                true
            }
        } else {
            notifyRequest.report("This characteristic does not support Notify or Indicate")
            false
        }
    }

    fun notifications(
        characteristic: BluetoothGattCharacteristic,
        holdOn: Boolean = false
    ): Flow<ByteArray> = callbackFlow {
        launch {
            try {
                toggleNotification(characteristic, true).apply {
                    if (!this) throw IllegalStateException("Failed")
                }
                awaitCancellation()
            } finally {
                if (!holdOn) {
                    toggleNotification(characteristic, false)
                }
            }
        }
        _responseShare.filter {
            it is BleResponse.OnCharacteristicChanged && it.characteristic.uuid == characteristic.uuid
        }.collect {
            val value = (it as BleResponse.OnCharacteristicChanged).characteristic.value
            send(value)
        }
        //awaitClose {}
    }

    private suspend inline fun <reified T : BleResponse> BleRequest.gattExecute(
        crossinline operation: BluetoothGatt.() -> Boolean
    ): T? {
        val request = this
        if (isConnectionClosed()) return null
        return try {
            bleOperationMutex.withLock {
                if (isConnectionClosed()) return@withLock null
                val success = mGatt?.operation() ?: false
                if (!success) {
                    report("Native method exception")
                    return@withLock null
                } else {
                    report()
                }
                withTimeout(5000) {
                    if (request is BleRequest.RequestMTU) {
                        responseShared.first {
                            it is BleResponse.OnMtuChanged
                        }
                    } else {
                        internalResponseChannel.receive()
                    } as T
                }
            }
        } catch (e: Exception) {
            report(e.message ?: "busy")
            null
        }
    }

    private fun BleRequest.report(msg: String? = null) {
        if (msg != null) {
            result = false
            errorMessage = msg
        } else {
            result = true
            errorMessage = null
        }
        //todo log的开关
        if (this.result) {
//            Log.d(TAG, this.getDesc())
        } else {
            Log.w(TAG, this.getDesc())
        }
        if (coroutineScope.isActive) {
            coroutineScope.launch {
                _requestShare.emit(this@report)
            }
        }
    }

    private fun BleResponse.report() {
        val response = this
        //todo log的开关
        if (response.isSuccess()) {
//            Log.i(TAG, response.getDesc())
//            Log.d(TAG, "data: ${response.getValue()?.toHexString(',')}")
        } else {
            Log.e(TAG, response.getDesc())
        }
        if (!coroutineScope.isActive) return
        coroutineScope.launch {
            //有两个例外是因为它们可以是外设主动发起，会打破request-response的关系，造成顺序混乱
            when (response) {
                is BleResponse.OnCharacteristicChanged, is BleResponse.OnMtuChanged, is BleResponse.OnConnectionStateChange -> {}
                else -> internalResponseChannel.send(response)
            }
            _responseShare.emit(response)
        }
    }


    @Synchronized
    private fun refreshDeviceCache() {
        try {
            val refresh = BluetoothGatt::class.java.getMethod("refresh")
            if (mGatt != null) {
                val success = refresh.invoke(mGatt) as Boolean
                Log.d(TAG, "refreshDeviceCache, is success:  $success")
            }
        } catch (e: java.lang.Exception) {
            Log.d(TAG, "exception occur while refreshing device: " + e.message)
            e.printStackTrace()
        }
    }

}


